#region Copyright (c) 2006-2010 Widgetsphere LLC, All Rights Reserved
//--------------------------------------------------------------------- *
//                          Widgetsphere  LLC                           *
//             Copyright (c) 2006-2010 All Rights reserved              *
//                                                                      *
//                                                                      *
//This file and its contents are protected by United States and         *
//International copyright laws.  Unauthorized reproduction and/or       *
//distribution of all or any portion of the code contained herein       *
//is strictly prohibited and will result in severe civil and criminal   *
//penalties.  Any violations of this copyright will be prosecuted       *
//to the fullest extent possible under law.                             *
//                                                                      *
//THE SOURCE CODE CONTAINED HEREIN AND IN RELATED FILES IS PROVIDED     *
//TO THE REGISTERED DEVELOPER FOR THE PURPOSES OF EDUCATION AND         *
//TROUBLESHOOTING. UNDER NO CIRCUMSTANCES MAY ANY PORTION OF THE SOURCE *
//CODE BE DISTRIBUTED, DISCLOSED OR OTHERWISE MADE AVAILABLE TO ANY     *
//THIRD PARTY WITHOUT THE EXPRESS WRITTEN CONSENT OF WIDGETSPHERE LLC   *
//                                                                      *
//UNDER NO CIRCUMSTANCES MAY THE SOURCE CODE BE USED IN WHOLE OR IN     *
//PART, AS THE BASIS FOR CREATING A PRODUCT THAT PROVIDES THE SAME, OR  *
//SUBSTANTIALLY THE SAME, FUNCTIONALITY AS ANY WIDGETSPHERE PRODUCT.    *
//                                                                      *
//THE REGISTERED DEVELOPER ACKNOWLEDGES THAT THIS SOURCE CODE           *
//CONTAINS VALUABLE AND PROPRIETARY TRADE SECRETS OF WIDGETSPHERE,      *
//INC.  THE REGISTERED DEVELOPER AGREES TO EXPEND EVERY EFFORT TO       *
//INSURE ITS CONFIDENTIALITY.                                           *
//                                                                      *
//THE END USER LICENSE AGREEMENT (EULA) ACCOMPANYING THE PRODUCT        *
//PERMITS THE REGISTERED DEVELOPER TO REDISTRIBUTE THE PRODUCT IN       *
//EXECUTABLE FORM ONLY IN SUPPORT OF APPLICATIONS WRITTEN USING         *
//THE PRODUCT.  IT DOES NOT PROVIDE ANY RIGHTS REGARDING THE            *
//SOURCE CODE CONTAINED HEREIN.                                         *
//                                                                      *
//THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE.              *
//--------------------------------------------------------------------- *
#endregion
using System;
using System.Linq;
using Widgetsphere.Generator.Common.GeneratorFramework;
using Widgetsphere.Generator.Models;
using System.Text;
using Widgetsphere.Generator.Common.Util;

namespace Widgetsphere.Generator.EFDAL.Generators.EFCSDL
{
	public class CSDLTemplate : EFDALBaseTemplate
	{
		private StringBuilder sb = new StringBuilder();

		public CSDLTemplate(ModelRoot model)
		{
			_model = model;
		}

		#region BaseClassTemplate overrides
		public override string FileName
		{
			get { return string.Format("{0}.csdl", _model.ProjectName); }
		}


		public override string FileContent
		{
			get
			{
				try
				{
					GenerateEdmxFile();
					return sb.ToString();
				}
				catch (Exception ex)
				{
					throw;
				}
			}
		}

		#endregion

		#region GenerateContent

		public void GenerateEdmxFile()
		{
			sb.AppendLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
			sb.AppendFormat("<Schema Namespace=\"{0}\" Alias=\"Self\" xmlns=\"http://schemas.microsoft.com/ado/2006/04/edm\">", this.DefaultNamespace).AppendLine();
			GenerateEntityContainer();
			GenerateEntityTypes();
			GenerateRelations();
			sb.AppendLine("</Schema>");
		}

		private void GenerateRelations()
		{
			foreach (Relation relation in _model.Database.Relations)
			{
				Table parentEntity = (Table)relation.ParentTableRef.Object;
				Table childEntity = (Table)relation.ChildTableRef.Object;
				if (parentEntity != null && childEntity != null && parentEntity.Generated && childEntity.Generated)
				{
					if (relation.IsOneToOne)
					{
						StringBuilder parentColumns = new StringBuilder();
						StringBuilder childColumns = new StringBuilder();
						foreach (ColumnRelationship columnRelation in relation.ColumnRelationships)
						{
							Column childColumn = (Column)columnRelation.ChildColumnRef.Object;
							Column parentColumn = (Column)columnRelation.ParentColumnRef.Object;
							parentColumns.AppendFormat("			<PropertyRef Name=\"{0}\" />", parentColumn.PascalName).AppendLine();
							childColumns.AppendFormat("			<PropertyRef Name=\"{0}\" />", childColumn.PascalName).AppendLine();
						}

						sb.AppendFormat("	<Association Name=\"{0}\">", relation.GetCodeFkName()).AppendLine();
						sb.AppendFormat("		<End Role=\"{2}{0}\" Type=\"{1}.{0}\" Multiplicity=\"1\" />", parentEntity.PascalName, this.DefaultNamespace, relation.RoleName).AppendLine();
						sb.AppendFormat("		<End Role=\"{2}{0}List\" Type=\"{1}.{0}\" Multiplicity=\"1\" />", childEntity.PascalName, this.DefaultNamespace, relation.RoleName).AppendLine();
						sb.AppendLine("		<ReferentialConstraint>");
						sb.AppendFormat("			<Principal Role=\"{1}{0}\">", parentEntity.PascalName, relation.RoleName).AppendLine();
						sb.Append(parentColumns.ToString());
						sb.AppendLine("			</Principal>");
						sb.AppendFormat("			<Dependent Role=\"{1}{0}List\">", childEntity.PascalName, relation.RoleName).AppendLine();
						sb.Append(childColumns.ToString());
						sb.AppendLine("			</Dependent>");
						sb.AppendLine("		</ReferentialConstraint>");
						sb.AppendLine("	</Association>");
					}
					else
					{
						sb.AppendFormat("  <Association Name=\"{0}\">", relation.GetCodeFkName()).AppendLine();
						if (relation.IsRequired())
							sb.AppendFormat("    <End Role=\"{3}{0}\" Type=\"{1}.{2}\" Multiplicity=\"1\" />", parentEntity.PascalName, this.DefaultNamespace, parentEntity.PascalName, relation.RoleName).AppendLine();
						else
							sb.AppendFormat("    <End Role=\"{3}{0}\" Type=\"{1}.{2}\" Multiplicity=\"0..1\" />", parentEntity.PascalName, this.DefaultNamespace, parentEntity.PascalName, relation.RoleName).AppendLine();

						sb.AppendFormat("    <End Role=\"{3}{0}List\" Type=\"{1}.{2}\" Multiplicity=\"*\" />", childEntity.PascalName, this.DefaultNamespace, childEntity.PascalName, relation.RoleName).AppendLine();


						StringBuilder parentColumns = new StringBuilder();
						StringBuilder childColumns = new StringBuilder();
						foreach (ColumnRelationship columnRelation in relation.ColumnRelationships)
						{
							Column childColumn = (Column)columnRelation.ChildColumnRef.Object;
							Column parentColumn = (Column)columnRelation.ParentColumnRef.Object;
							parentColumns.AppendFormat("			<PropertyRef Name=\"{0}\" />", parentColumn.PascalName).AppendLine();
							childColumns.AppendFormat("			<PropertyRef Name=\"{0}\" />", childColumn.PascalName).AppendLine();
						}
						//sb.AppendLine("		<ReferentialConstraint>");
						//sb.AppendFormat("			<Principal Role=\"{1}{0}\">", parentEntity.PascalName, relation.RoleName).AppendLine();
						//sb.Append(parentColumns.ToString());
						//sb.AppendLine("			</Principal>");
						//sb.AppendFormat("			<Dependent Role=\"{1}{0}List\">", childEntity.PascalName, relation.RoleName).AppendLine();
						//sb.Append(childColumns.ToString());
						//sb.AppendLine("			</Dependent>");
						//sb.AppendLine("		</ReferentialConstraint>");
						
						sb.AppendLine("  </Association>");
					}
				}
			}
		}

		private void GenerateEntityTypes()
		{
			foreach (Table currentTable in _model.Database.Tables.Where(x => x.Generated))
			{
				//If there is a parent table then add its base type
				string baseTable = "";
				if (currentTable.ParentTable != null)
					baseTable = " BaseType=\"" + this.DefaultNamespace + "." + currentTable.ParentTable.PascalName + "\" ";

				sb.AppendLine("	<EntityType Name=\"" + currentTable.PascalName + "\"" + baseTable + ">");

				//Keys can only be defined by base tables
				if (currentTable.ParentTable == null)
				{
					sb.AppendLine("		<Key>");
					foreach (Column column in currentTable.PrimaryKeyColumns.Where(x => x.Generated).OrderBy(x => x.Name))
					{
						sb.AppendFormat("			<PropertyRef Name=\"{0}\" />", column.PascalName).AppendLine();
					}
					sb.AppendLine("		</Key>");
				}

				foreach (Column column in currentTable.GetColumns().Where(x => x.Generated).OrderBy(x => x.Name))
				{
					//If NOT a derived table then take normal path
					if (currentTable.ParentTable == null)
					{
						if (column.PrimaryKey || !column.IsForeignKey())
						{
							sb.Append(GetPropertyNode(column));
						}
					}
					else //Derived table - do not include primary keys
					{
						if (!column.PrimaryKey && !column.IsForeignKey())
						{
							sb.Append(GetPropertyNode(column));
						}
					}
				}

				this.GenerateAuditColumns(currentTable);
				foreach (Relation relation in currentTable.ParentRoleRelations)
				{
					Table parentEntity = (Table)relation.ParentTableRef.Object;
					Table childEntity = (Table)relation.ChildTableRef.Object;
					if (parentEntity != null && childEntity != null && parentEntity.Generated && childEntity.Generated && relation.IsGenerated)
						sb.AppendFormat("		<NavigationProperty Name=\"{0}{4}List\" Relationship=\"{2}.{3}\" FromRole=\"{0}{1}\" ToRole=\"{0}{4}List\" />", relation.RoleName, parentEntity.PascalName, this.DefaultNamespace, relation.GetCodeFkName(), childEntity.PascalName).AppendLine();
				}

				foreach (Relation relation in currentTable.ChildRoleRelations)
				{
					Table parentEntity = (Table)relation.ParentTableRef.Object;
					Table childEntity = (Table)relation.ChildTableRef.Object;
					if (parentEntity != null && childEntity != null && parentEntity.Generated && childEntity.Generated && relation.IsGenerated)
						sb.AppendFormat("		<NavigationProperty Name=\"{0}{1}\" Relationship=\"{2}.{3}\" FromRole=\"{0}{4}List\" ToRole=\"{0}{1}\" />", relation.RoleName, parentEntity.PascalName, this.DefaultNamespace, relation.GetCodeFkName(), childEntity.PascalName).AppendLine();
				}

				sb.AppendLine("	</EntityType>");
			}
		}		

		private void GenerateAuditColumns(Table currentTable)
		{
			//Audit fields are NOT generated for derived tables
			if (currentTable.ParentTable != null) return;

			if (currentTable.AllowModifiedAudit)
			{
				sb.AppendFormat("		<Property Name=\"{0}\" Type=\"String\" MaxLength=\"50\" Unicode=\"false\" FixedLength=\"false\" />", StringHelper.DatabaseNameToPascalCase(_model.Database.ModifiedByColumnName)).AppendLine();
				sb.AppendFormat("		<Property Name=\"{0}\" Type=\"DateTime\" />", StringHelper.DatabaseNameToPascalCase(_model.Database.ModifiedDateColumnName)).AppendLine();
			}

			if (currentTable.AllowCreateAudit)
			{
				sb.AppendFormat("		<Property Name=\"{0}\" Type=\"String\" MaxLength=\"50\" Unicode=\"false\" FixedLength=\"false\" />", StringHelper.DatabaseNameToPascalCase(_model.Database.CreatedByColumnName)).AppendLine();
				sb.AppendFormat("		<Property Name=\"{0}\" Type=\"DateTime\" />", StringHelper.DatabaseNameToPascalCase(_model.Database.CreatedDateColumnName)).AppendLine();
			}

			if (currentTable.AllowTimestamp)
			{
				sb.AppendFormat("		<Property Name=\"{0}\" Type=\"Binary\" Nullable=\"false\" MaxLength=\"8\" FixedLength=\"true\" />", StringHelper.DatabaseNameToPascalCase(_model.Database.TimestampColumnName)).AppendLine();
			}

		}

		private void GenerateEntityContainer()
		{
			sb.AppendFormat("	<EntityContainer Name=\"{0}Entities\">", _model.ProjectName).AppendLine();
			foreach (Table currentTable in _model.Database.Tables)
			{
				if (currentTable.Generated)
				{
					sb.AppendFormat("		<EntitySet Name=\"{0}\" EntityType=\"{1}.{0}\" />", currentTable.PascalName, this.DefaultNamespace).AppendLine();
				}
			}

			foreach (Relation relation in _model.Database.Relations)
			{
				Table parentEntity = (Table)relation.ParentTableRef.Object;
				Table childEntity = (Table)relation.ChildTableRef.Object;
				if (parentEntity != null && childEntity != null && parentEntity.Generated && childEntity.Generated && relation.IsGenerated)
				{
					sb.AppendFormat("		<AssociationSet Name=\"{0}\" Association=\"{1}.{0}\">", relation.GetCodeFkName(), this.DefaultNamespace).AppendLine();
					sb.AppendFormat("			<End Role=\"{0}{1}\" EntitySet=\"{1}\" />", relation.RoleName, parentEntity.PascalName).AppendLine();
					sb.AppendFormat("			<End Role=\"{0}{1}List\" EntitySet=\"{1}\" />", relation.RoleName, childEntity.PascalName).AppendLine();
					sb.AppendFormat("		</AssociationSet>").AppendLine();
				}
			}
			sb.AppendLine("	</EntityContainer>");
		}


		private string GetPropertyNode(Column column)
		{
			StringBuilder propertyString = new StringBuilder();

			//Attributes Name and Type
			propertyString.AppendFormat("		<Property Name=\"{0}\" Type=\"{1}\" ", column.PascalName, column.EFCodeType());

			//Attribute Nullable
			if (!column.AllowNull)
			{
				propertyString.Append("Nullable=\"false\" ");
			}

			//Attribute MaxLength
			if (!string.IsNullOrEmpty(column.EFGetCodeMaxLengthString()))
			{
				propertyString.AppendFormat("MaxLength=\"{0}\" ", column.EFGetCodeMaxLengthString());
			}

			//Attribute Precision Scale
			if (column.EFSupportsPrecision())
			{
				propertyString.AppendFormat("Precision=\"{0}\" Scale=\"{1}\" ", column.Length.ToString(), column.Scale);
			}

			//Attribute Unicode
			if (column.EFUnicode().HasValue)
			{
				string unicodeString = column.EFUnicode().Value ? "true" : "false";
				propertyString.AppendFormat("Unicode=\"{0}\" ", unicodeString);
			}

			//Attribute FixedLength
			if (column.EFIsFixedLength().HasValue)
			{
				string isFixedLengthString = column.EFIsFixedLength().Value ? "true" : "false";
				propertyString.AppendFormat("FixedLength=\"{0}\" ", isFixedLengthString);
			}

			//Primary Key
			if (column.PrimaryKey)
			{
				if (column.Identity == IdentityTypeConstants.Database)
				{
					propertyString.AppendFormat("DefaultValue=\"-1\" ");
					propertyString.Append("a:SetterAccess=\"Private\" xmlns:a=\"http://schemas.microsoft.com/ado/2006/04/codegeneration\" ");
				}
			}

			propertyString.Append("/>").AppendLine();
			return propertyString.ToString();
		}

		#endregion


	}
}